fix: redact project credentials on egress - #1702
Conversation
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughProjects now redact credentials before sharing, exporting, embedding, collaboration, and Python serialization. Local saves can retain credentials when explicitly requested. Sharing reports removed credential fields, with updated localized messages and regression coverage. ChangesCredential-safe project egress
Estimated code review effort: 4 (Complex) | ~45 minutes Sequence Diagram(s)sequenceDiagram
participant Desktop
participant Core
participant ShareService
participant Collaboration
participant PythonAPI
Desktop->>Core: redactProjectCredentials(project)
Core-->>Desktop: sanitized project and redaction count
Desktop->>ShareService: upload serialized sanitized project
Desktop->>Collaboration: send sanitized snapshot
PythonAPI->>Core: redact project for export
Core-->>PythonAPI: sanitized project
Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 3 | ❌ 2❌ Failed checks (2 warnings)
✅ Passed checks (3 passed)
✨ Finishing Touches 💡 1📝 Generate docstrings 💡
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
🔍 Cloudflare PR preview
|
There was a problem hiding this comment.
🟡 Not ready to approve
There are security-relevant edge cases where credential redaction can be bypassed (depth cap returning unredacted values) and Python URL param coverage is incomplete (missing subscription-key variants).
Once you've addressed the issues Copilot identified, you can request another Copilot review.
This review doesn't count toward merge requirements. Sign up for the private preview to control whether Copilot approvals count.
Pull request overview
This PR enforces a “no credentials on egress” invariant by introducing centralized credential redaction in @geolibre/core and applying it consistently across share uploads, standalone HTML export, collaboration/embed snapshots, local save prompting, and Python serialization paths, with accompanying docs and test coverage.
Changes:
- Add
@geolibre/corecredential registry + redaction utilities (redactCredentials,redactProjectCredentials,redactUrlCredentials) and wire them into all external-project egress paths. - Update UI/egress flows to (a) prompt before keeping credentials in local saves and (b) notify after sharing when credentials were removed.
- Add tests covering core redaction + share/html/Python egress paths; document the invariant and plugin-settings behavior.
File summaries
| File | Description |
|---|---|
| tests/share-geolibre.test.ts | Adds coverage asserting share uploads redact credentials before network egress. |
| tests/share-fetch.test.ts | Updates fixture project content to use core serialization helpers. |
| tests/project-credentials.test.ts | Introduces unit tests for the central credential registry and redaction behavior (including idempotency). |
| tests/html-export.test.ts | Adds coverage asserting standalone HTML export payload redacts credentials. |
| python/tests/test_scripting.py | Adds coverage ensuring Python to_project(), to_html(), and save_project() redact by default. |
| python/src/geolibre/project.py | Implements Python-side credential redaction for project dicts and URL-like strings. |
| python/src/geolibre/geolibre.py | Applies redaction to HTML export + adds keep_credentials flags for project egress APIs. |
| packages/core/src/index.ts | Re-exports new credential redaction APIs from core. |
| packages/core/src/credentials.ts | Adds the core credential registry and redaction implementation used across egress paths. |
| docs/project-format.md | Documents the credential-redaction invariant for projects leaving the local workspace. |
| docs/plugin-api.md | Documents that plugins.settings is treated as sensitive and omitted on egress by default. |
| apps/geolibre-desktop/src/lib/share-geolibre.ts | Forces share uploads to validate + redact content before sending requests. |
| apps/geolibre-desktop/src/lib/html-export.ts | Redacts credentials before embedding project JSON in standalone HTML exports. |
| apps/geolibre-desktop/src/lib/build-project-snapshot.ts | Adds a shared “egress snapshot” builder that redacts credentials for collaboration/embed. |
| apps/geolibre-desktop/src/hooks/useProjectFileActions.ts | Prompts users before saving credential-bearing projects and relies on centralized redaction output. |
| apps/geolibre-desktop/src/hooks/useEmbedBridge.ts | Switches embed snapshots to the redacted egress snapshot builder. |
| apps/geolibre-desktop/src/hooks/useCollaboration.ts | Switches collaboration snapshots to the redacted egress snapshot builder. |
| apps/geolibre-desktop/src/components/layout/TopToolbar.tsx | Redacts + counts removed credential fields when preparing share payloads. |
| apps/geolibre-desktop/src/components/layout/ShareProjectDialog.tsx | Displays a post-share notice when credential fields were removed. |
| apps/geolibre-desktop/src/i18n/locales/en.json | Adds/updates strings for credential stripping prompts and post-share notice. |
| apps/geolibre-desktop/src/i18n/locales/ar.json | Adds/updates localized strings for credential stripping + share notice. |
| apps/geolibre-desktop/src/i18n/locales/de.json | Adds/updates localized strings for credential stripping + share notice. |
| apps/geolibre-desktop/src/i18n/locales/es.json | Adds/updates localized strings for credential stripping + share notice. |
| apps/geolibre-desktop/src/i18n/locales/fr.json | Adds/updates localized strings for credential stripping + share notice. |
| apps/geolibre-desktop/src/i18n/locales/hi.json | Adds/updates localized strings for credential stripping + share notice. |
| apps/geolibre-desktop/src/i18n/locales/id.json | Adds/updates localized strings for credential stripping + share notice. |
| apps/geolibre-desktop/src/i18n/locales/it.json | Adds/updates localized strings for credential stripping + share notice. |
| apps/geolibre-desktop/src/i18n/locales/ja.json | Adds/updates localized strings for credential stripping + share notice. |
| apps/geolibre-desktop/src/i18n/locales/ka.json | Adds/updates localized strings for credential stripping + share notice. |
| apps/geolibre-desktop/src/i18n/locales/ko.json | Adds/updates localized strings for credential stripping + share notice. |
| apps/geolibre-desktop/src/i18n/locales/nl.json | Adds/updates localized strings for credential stripping + share notice. |
| apps/geolibre-desktop/src/i18n/locales/pt.json | Adds/updates localized strings for credential stripping + share notice. |
| apps/geolibre-desktop/src/i18n/locales/ru.json | Adds/updates localized strings for credential stripping + share notice. |
| apps/geolibre-desktop/src/i18n/locales/th.json | Adds/updates localized strings for credential stripping + share notice. |
| apps/geolibre-desktop/src/i18n/locales/tr.json | Adds/updates localized strings for credential stripping + share notice. |
| apps/geolibre-desktop/src/i18n/locales/zh.json | Adds/updates localized strings for credential stripping + share notice. |
Review details
- Files reviewed: 36/36 changed files
- Comments generated: 2
- Review effort level: Lite
We're testing this review assessment. Please use 👍 or 👎 to tell us if it's correct.
- Fail closed when project configuration exceeds the redaction depth cap. - Redact subscription-key URL parameters from Python project egress.
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@apps/geolibre-desktop/src/components/layout/TopToolbar.tsx`:
- Around line 2015-2019: The project export result in the surrounding
ShareProject flow currently reports redacted.redactedPaths.length, which counts
paths rather than removed credential fields. Update the core redaction helper to
return the total number of removed fields and use that value for redactedCount
in the returned object; otherwise change the localized notice to explicitly
describe redaction paths or groups.
In `@apps/geolibre-desktop/src/i18n/locales/tr.json`:
- Line 1176: Update the Turkish credentialsRemoved translation to use “aracılı
bir referans” instead of “aracılı bir başvuru,” matching the
brokered-credential-reference meaning defined by en.json.
In `@packages/core/src/credentials.ts`:
- Around line 202-205: Update redactProjectCredentials to sanitize
project.basemapStyleUrl with redactUrlCredentials before constructing the
returned project; include the redacted URL only when it differs from the
original, while preserving the existing spread and other project fields.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: d647377f-e9a6-4e3d-967a-e2b38cb1c740
📒 Files selected for processing (36)
apps/geolibre-desktop/src/components/layout/ShareProjectDialog.tsxapps/geolibre-desktop/src/components/layout/TopToolbar.tsxapps/geolibre-desktop/src/hooks/useCollaboration.tsapps/geolibre-desktop/src/hooks/useEmbedBridge.tsapps/geolibre-desktop/src/hooks/useProjectFileActions.tsapps/geolibre-desktop/src/i18n/locales/ar.jsonapps/geolibre-desktop/src/i18n/locales/de.jsonapps/geolibre-desktop/src/i18n/locales/en.jsonapps/geolibre-desktop/src/i18n/locales/es.jsonapps/geolibre-desktop/src/i18n/locales/fr.jsonapps/geolibre-desktop/src/i18n/locales/hi.jsonapps/geolibre-desktop/src/i18n/locales/id.jsonapps/geolibre-desktop/src/i18n/locales/it.jsonapps/geolibre-desktop/src/i18n/locales/ja.jsonapps/geolibre-desktop/src/i18n/locales/ka.jsonapps/geolibre-desktop/src/i18n/locales/ko.jsonapps/geolibre-desktop/src/i18n/locales/nl.jsonapps/geolibre-desktop/src/i18n/locales/pt.jsonapps/geolibre-desktop/src/i18n/locales/ru.jsonapps/geolibre-desktop/src/i18n/locales/th.jsonapps/geolibre-desktop/src/i18n/locales/tr.jsonapps/geolibre-desktop/src/i18n/locales/zh.jsonapps/geolibre-desktop/src/lib/build-project-snapshot.tsapps/geolibre-desktop/src/lib/html-export.tsapps/geolibre-desktop/src/lib/share-geolibre.tsdocs/plugin-api.mddocs/project-format.mdpackages/core/src/credentials.tspackages/core/src/index.tspython/src/geolibre/geolibre.pypython/src/geolibre/project.pypython/tests/test_scripting.pytests/html-export.test.tstests/project-credentials.test.tstests/share-fetch.test.tstests/share-geolibre.test.ts
- redact credential parameters from basemap style URLs - report individual redacted fields and correct Turkish wording
There was a problem hiding this comment.
Actionable comments posted: 3
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
python/tests/test_scripting.py (1)
316-351: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winExtend this test to prove encoded redaction and opt-in file retention.
The fixture covers only plain
subscription-key. Add an encoded form such assubscription%2Dkey=...and assert that its secret is absent fromserialized,html, andsaved.The final assertion checks only that plugin settings are non-empty. Assert a known credential value for
keep_credentials=True. Also callsave_project(..., keep_credentials=True)and assert that the retained secret is present in that file.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@python/tests/test_scripting.py` around lines 316 - 351, Extend the test fixture in the project serialization flow to include an encoded query key such as subscription%2Dkey with a known secret, then add that secret to the redaction assertions for serialized, HTML, and saved output. Update the keep_credentials=True assertion to verify the known plugin credential value, and save with keep_credentials=True to a separate file and assert that the retained secret appears there.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@packages/core/src/credentials.ts`:
- Around line 124-130: In redactConfigurationValue, evaluate the depth >=
MAX_REDACT_DEPTH fail-closed check before the scalar string branch and its
redactUrlCredentials return. Preserve the existing redactedPaths update and
undefined return so strings at the traversal boundary are omitted like other
deeply nested values.
In `@python/src/geolibre/project.py`:
- Around line 45-46: Update _redact_url to URL-decode each query parameter name
before checking it against _CREDENTIAL_URL_PARAMS, while preserving the original
query pair when retaining parameters and continuing to redact matching
credentials.
In `@tests/project-credentials.test.ts`:
- Around line 82-91: Strengthen the test in the “fails closed when configuration
exceeds the traversal depth” case by placing a marker scalar exactly at or
beyond MAX_REDACT_DEPTH, rather than only checking a deeply nested serialized
object. Assert that this scalar marker is removed from the serialized result and
that its specific redacted path is included in result.redactedPaths.
---
Outside diff comments:
In `@python/tests/test_scripting.py`:
- Around line 316-351: Extend the test fixture in the project serialization flow
to include an encoded query key such as subscription%2Dkey with a known secret,
then add that secret to the redaction assertions for serialized, HTML, and saved
output. Update the keep_credentials=True assertion to verify the known plugin
credential value, and save with keep_credentials=True to a separate file and
assert that the retained secret appears there.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: f920822e-16ee-4746-a96e-efcceabe2261
📒 Files selected for processing (4)
packages/core/src/credentials.tspython/src/geolibre/project.pypython/tests/test_scripting.pytests/project-credentials.test.ts
🔍 GitHub Pages PR preview
|
Code reviewBugs
Security
Quality
CLAUDE.md
Performance — no notable issues found; the redaction pass runs once per egress event on already-small project objects. |
- sanitize basemap and plugin manifest URLs across TypeScript and Python - harden URL parameter and userinfo redaction edge cases - align collaboration snapshots and credential counts
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@packages/core/src/credentials.ts`:
- Around line 67-73: Update isCredentialParam to decode the normalized name
before applying lowercase normalization. Ensure percent-encoded alphabetic
characters are lowercased after decoding, so URL_CREDENTIAL_PARAMS and the
x-amz- prefix checks handle encoded credential names correctly while preserving
malformed-input handling.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: 435d91b7-6bbd-49f1-941e-4c23194e3118
📒 Files selected for processing (6)
apps/geolibre-desktop/src/hooks/useCollaboration.tsapps/geolibre-desktop/src/hooks/useProjectFileActions.tspackages/core/src/credentials.tspython/src/geolibre/project.pypython/tests/test_scripting.pytests/project-credentials.test.ts
Code reviewBugs
Quality
No issues found in the i18n translation additions, the double-redaction in |
|
All inline comments are posted. Here is the final review summary. Code reviewBugs
Security
Performance
Quality
CLAUDE.md
|
- reuse the central structural-key registry for Layer Library exports - redact Python layer source paths and document processing scope
Code reviewBugs
Quality
Security / Performance / CLAUDE.md
|
- distinguish the co-located anywidget host from external embed egress - retain redaction for standalone HTML and iframe hosts
|
All inline comments are posted. Here is the final summary. Code reviewBugs
Security
Performance
Quality
CLAUDE.md
|
- Extend PROJECT_CREDENTIAL_FIELDS.layerConfiguration with the credential names the URL registry already stripped but the object-key denylist missed (secret, bearer, auth, authKey, sasToken, subscriptionKey, signature, pwd), so a layer source storing one as a plain JSON field is no longer shipped in a "redacted" export. - Derive URL_CREDENTIAL_PARAMS from that same list plus the names that are credentials only in a query string (`key` and the Azure SAS positional parameters sv/sr/st/se/sp/sig/skoid), and document why they stay URL-only: as configuration field names they collide with ordinary state such as an ArcGIS `sr`. - Match names through a shared normalizer so apiKey/api_key/api-key/APIKEY are one entry on both sides, and route the Layer Library export sweep through the same isCredentialFieldName predicate instead of its own lowercase set, so the two paths cannot drift. - Cover the new spellings, and the deliberately-kept `sr`/`key` fields, in tests/project-credentials.test.ts.
- packages/core/src/layer-library.ts: `redactSourceValue` now fails closed past its depth cap instead of returning the value unswept, matching the fail-closed stance `redactConfigurationValue` takes in credentials.ts. Only the exported bundle loses the value; the local IndexedDB entry is untouched. - useEmbedBridge.ts / embedHost.ts: rewrite the trust-model docs, which still claimed the host receives the entire project state. Snapshots are redacted by default, and the `trustedWidget` opt-in is documented as self-declared by the host — a fidelity switch for the co-located widget, not a security boundary; the boundary is still the framing context. - useProjectFileActions.ts / ProjectFileDialogs.tsx: rename EnvStripPrompt/envStripPrompt/askStripEnvVars to the credential-neutral CredentialStripPrompt/credentialStripPrompt/askStripCredentials, since the prompt now covers every credential type, not just env vars. - docs/python.md: document that to_project()/save_project()/to_html() redact by default and that keep_credentials=True opts back out.
- Add a regression test for the fail-closed depth cap in redactSourceValue: a credential nested past MAX_REDACT_DEPTH must not reach the exported bundle. Fails against the previous fail-open behavior.
|
Both inline comments posted successfully. Now finalizing the summary. Code reviewBugs
Security
Performance
Quality
CLAUDE.md
|
- python/src/geolibre/project.py: `_CREDENTIAL_FIELD_NAMES` was missing the names added to the TS registry (secret, bearer, auth, authKey, sasToken, subscriptionKey, signature, pwd), so `to_project()`/`save_project()`/ `to_html()` could ship a credential the JS egress path strips. Derive `_CREDENTIAL_URL_PARAMS` from that same set plus the query-string-only names, and match through a `_normalize_credential_name` mirror of the JS normalizer, with a parity test in python/tests/test_scripting.py. - useProjectFileActions.ts: reword the stale HTML-export comment — this call site no longer strips environment variables itself; buildProjectHtml runs the central redaction pass.
| } | ||
|
|
||
| const layers = (project.layers ?? []).map((layer, index) => ({ | ||
| ...layer, |
There was a problem hiding this comment.
Nice, well-tested redaction design overall. One gap: the per-layer sweep only redacts source, metadata, and sourcePath (via ...layer then overriding those three) — layer.connection (in particular connection.lastError) passes through untouched.
Today's built-in refresh-error paths (layer-refresh.ts) only ever set lastError to generic strings like "Request failed with status 403", so nothing currently leaks. But connection.lastError is free-form text set from a caught error's .message, and it isn't in PROJECT_CREDENTIAL_FIELDS or swept by redactConfigurationValue. If a future refresh path (or a plugin driving connection) ever surfaces an error message that embeds the request URL (a common pattern, e.g. `Failed to fetch ${url}`), a token/API-key query parameter would ship unredacted through Share/HTML export/embed/collaboration. Worth adding connection to the sweep (or at least connection.lastError through redactUrlCredentials) for defense-in-depth, given the module's own stated goal is a "no-secret guarantee" for external egress. Low confidence this is exploitable today, but it's a blind spot in an otherwise very deliberately enumerated registry.
The Python mirror (python/src/geolibre/project.py, redact_credentials) has the identical gap since it also only touches source/metadata/sourcePath per layer.
Code reviewBugs
Security
Performance
Quality
CLAUDE.md
|
The egress redaction pass added in #1702 sweeps each layer's `source`, `metadata`, and `sourcePath`, but leaves `connection` untouched. `connection.lastError` is free-form text taken from a caught error, and a refresh path that words it as `Failed to fetch ${url}` would carry the request's credential parameters straight through Share, HTML export, embed, and collaboration. Today's built-in refresh paths only ever store generic status strings, so nothing leaks in practice — this closes the blind spot rather than a live bug, so the "no-secret guarantee" does not depend on how a future error message is phrased. - packages/core/src/credentials.ts: sweep `layer.connection` through `redactConfigurationValue` alongside the other three fields. - python/src/geolibre/project.py: the same field in the Python mirror. - Tests on both sides assert the token is stripped while the rest of the connection record (interval, timestamps) survives.
Summary
@geolibre/coreVerification
npm run buildnpm run test:frontend(5,051 passed, 1 skipped)Fixes #1680
Summary by CodeRabbit